#!/usr/bin/python

usage="""
otl2latex.py

Convert between a Vim Outliner (LaTeX document) and LaTeX document (Vim
Outliner)



Usage:
    otl2latex.py [file.otl,file.tex] [file.tex, file.otl]


    If only a single file name argument is passed to otl2latex.py, then then
    the output filetype will be determined from the file name argument and the
    prefix of the output file will be the same as the input file. 

    Thus:

        otl2latex.py beamer.otl

        will generate a new file called beamer.tex that has the LaTeX markup


        otl2latex.py beamer.otl newname.tex

        will generate the outpuyt file newname.tex


    Reverse parsing:

        otl2latex.py article.tex

        will generate a new otl file called article.otl


        Reverse parsing currently only supported for chapters and articles


Author: Serge Rey <sjsrey@gmail.com>
Version 0.4 (2009-09-28)
Version 0.3 (2009-07-23)
Version 0.2 (2008-09-26)
Version 0.1 (2007-01-21)
"""

import os,sys

class Line:
    """Class for markup lines"""
    def __init__(self, content):
        ntabs=content.count("\t")
        content=content.lstrip("\t")
        level = ntabs - content.count("\t")
        self.level=level
        self.content = content
        self.markup=0
        if content[0]=="|":
            self.markup=1

        self.bullet=0
        if len(content) > 2 and (content[2]=='*' or content[1]=='*'):
            self.bullet=1
        #print "%d: %s"%(self.bullet,content)
        
    def __str__(self):
        return self.content

    def __repr__(self):
        return self.content



class Manager:
    """Abstract class for LaTeX document classes"""
    def __init__(self, content, fileOut):
        self.content=content
        self.fileOut=open(fileOut,'w')
        self.parse()
        self.fileOut.write(self.markup)
        self.fileOut.close()
    def parse(self):
        self.lines=[ Line(line) for line in self.content]
        preambleStart=0
        nl=len(self.lines)
        id=zip(range(nl),self.lines)
        level1=[i for i,line in id if line.level==0]
        preambleEnd=level1[1]
        preamble=self.lines[0:preambleEnd]
        self.level1=level1
        preamble=[line.content.lstrip("|") for line in preamble[1:]]
        self.preamble="".join(preamble)
        self.documentLines=self.lines[preambleEnd:]

                



class Beamer(Manager):
    """Manager for Beamer document class"""
    def __init__(self, content,fileOut):
        self.bulletLevel = 0
        Manager.__init__(self, content, fileOut)

    def itemize(self,line):
        nstars=line.content.count("*")
        content=line.content.lstrip("|").lstrip().lstrip("*")
        self.currentBLevel = nstars - content.count("*")
        stuff=[]
        if self.currentBLevel == self.bulletLevel and line.bullet:
            mu='\\item '+line.content.lstrip("|").lstrip().lstrip("*")
        elif line.bullet and self.currentBLevel > self.bulletLevel:
            self.bulletLevel += 1
            stuff.append("\\begin{itemize}\n")
            mu='\\item '+line.content.lstrip("|").lstrip().lstrip("*")
        elif self.currentBLevel < self.bulletLevel and line.bullet:
            self.bulletLevel -= 1
            stuff.append("\\end{itemize}\n")
            mu='\\item '+line.content.lstrip("|").lstrip().lstrip("*")
        elif self.currentBLevel < self.bulletLevel:
            self.bulletLevel -= 1
            stuff.append("\\end{itemize}\n")
            mu=line.content.lstrip("|")
        else:
            pass
        return stuff,mu
        
    def parse(self):
        Manager.parse(self)


        body=[]
        prev=0
        frameOpen=0
        blockOpen=0
        frameCount=0
        blockCount=0
        
        for line in self.documentLines:
            if line.level==0:
                for i in range(0,self.bulletLevel):
                    self.bulletLevel -= 1
                    body.append("\\end{itemize}\n")
                if blockOpen:
                    body.append("\\end{block}")
                    blockOpen=0
                if frameOpen:
                    body.append("\\end{frame}")
                    frameOpen=0
                mu="\n\n\n\\section{%s}"%line.content.strip()
            elif line.level==1:
                for i in range(0,self.bulletLevel):
                    self.bulletLevel -= 1
                    body.append("\\end{itemize}\n")
                if blockOpen:
                    body.append("\\end{block}")
                    blockOpen=0
                if frameOpen:
                    body.append("\\end{frame}")
                    frameOpen=0
                mu="\n\n\\subsection{%s}"%line.content.strip()
            elif line.level==2:
                # check if this frame has blocks or is nonblocked
                if line.markup:
                    if line.bullet or self.bulletLevel:
                        stuff,mu=self.itemize(line)
                        if len(stuff) > 0:
                            for i in stuff:
                                body.append(i)
                    else:
                        mu=line.content.lstrip("|")
                        
                else:
                    for i in range(0,self.bulletLevel):
                        self.bulletLevel -= 1
                        body.append("\\end{itemize}\n")
                    if blockOpen:
                        body.append("\\end{block}")
                        blockOpen=0
                    if frameOpen:
                        body.append("\\end{frame}")
                    else:
                        frameOpen=1
                    # check for verbatim here
                    tmp=line.content.strip()
                    if tmp.count("@vb"):
                        tmp=tmp.split("@")[0]
                        mu="\n\n\\begin{frame}[containsverbatim]\n\t\\frametitle{%s}\n"%tmp
                    else:
                        mu="\n\n\\begin{frame}\n\t\\frametitle{%s}\n"%tmp
                    frameCount+=1
            elif line.level==3:
                # check if it is a block or body content
                if line.markup:
                    if line.bullet or self.bulletLevel:
                        stuff,mu=self.itemize(line)
                        if len(stuff) > 0:
                            for i in stuff:
                                body.append(i)
                    else:
                        mu=line.content.lstrip("\t")
                        mu=mu.lstrip("|")
                else:
                    for i in range(0,self.bulletLevel):
                        self.bulletLevel -= 1
                        body.append("\\end{itemize}\n")
                    #block title
                    if blockOpen:
                        body.append("\\end{block}")
                    else:
                        blockOpen=1
                    mu="\n\\begin{block}{%s}\n"%line.content.strip()
                    blockCount+=1
            else:
                mu=""
            body.append(mu)
        for i in range(0,self.bulletLevel):
            self.bulletLevel -= 1
            body.append("\\end{itemize}\n")
        if blockOpen:
            body.append("\\end{block}")
        if frameOpen:
            body.append("\\end{frame}")

        self.body=" ".join(body)
        self.markup=self.preamble
        self.markup+=self.body
        self.markup+="\n\\end{document}\n"

    
class Chapter(Manager):
    """Manager for a chapter in a latex book
    
    Chapter is an individual file for inclusion in a larger book via an
    "input" or "include"."""
    def __init__(self, content,fileOut):
        Manager.__init__(self, content, fileOut)

    def parse(self):
        Manager.parse(self)
        body=[]
        n=len(self.documentLines)
        for i,line in enumerate(self.documentLines):
            #print line.content
            mu=""
            if line.markup:
                mu=line.content.lstrip("|")
                if not mu.isspace():
                    mu=mu.lstrip()
            else:
                section=line.content.strip()
                #lab=""
                #if section.count("@"):
                #    tmp=section.split("@")
                #    section=tmp[0]
                #    label=tmp[1]
                if line.level==0:
                    mu="\n\n\\section{%s}"%line.content.strip()
                elif line.level==1:
                    mu="\n\n\\subsection{%s}"%line.content.strip()
                elif line.level==2:
                    mu="\n\n\\subsubsection{%s}"%line.content.strip()
                else:
                    pass

                # check if next line is a label
                if i < n-1:
                    nextLine=self.documentLines[i+1].content
                    if not nextLine.count("\\label{"):
                        mu+="\n\n" 
            body.append(mu)
        body=" ".join(body)
        self.markup=self.preamble+"\n"+body



class ReverseChapter(Manager):
    """Manager for a creating an otl file from  a latex book chapter
    
    Chapter is an individual file for inclusion in a larger book via an
    "input" or "include"."""
    def __init__(self, content,fileOut):
        Manager.__init__(self, content, fileOut)


    def parse(self):
        lines=self.content

        # trim blank lines at the end of the file
        while 1:
            if lines[-1].isspace():
                lines.pop()
            else:
                break
        lineTypes=[" "]*len(lines)
        for i,line in enumerate(lines):
            if line.isspace():
                print 'isspace'
                print i,line
                lineTypes[i]='blank'
            else:
                print 'not'
                print i, line
                tokens=["\chapter","\section","\subsection","\subsubsection"]
                lineType='body'
                for token in tokens:
                    if line.count(token):
                        if line.index(token)==0:
                            lineType='header'
                lineTypes[i]=lineType
        n=len(lineTypes)
        blanks=[]
        for i in range(n-2):
            trip=lineTypes[i:i+3]
            if trip[1]=='blank':
                if trip!=["body","blank","body"]:
                    blanks.append(i+1)
        blanks.sort()
        self.markup='blank'
        while blanks:
            p=blanks.pop()
            lines.pop(p)
        self.content=lines
        mu=""
        indentLevel=0
        newLines=[]
        blanks=[] 
        for c,line in enumerate(self.content):
            line=line.strip()
            if line.count("\chapter{"):
                print 'chapter'
                indentLevel=0
                newLine="preamble\n"
                if line.count("}")==2:
                    title,label=line.split("}",1)
                    title=title.split("{")[1]
                    label=label.split("{")[1].split("}")[0]
                else:
                    title=line.split("{")[1].split("}")[0]
                    label="NONE"
                #newLine+="\t@title %s\n\t@label %s"%(title,label)
                newLine+="\t"+line
            elif line.count("section{"):
                if line.count("\section"):
                    indentLevel=0
                elif line.count("\subsection"):
                    indentLevel=1
                elif line.count("\subsubsection"):
                    indentLevel=2
                if line.count("}") == 2:
                    section,label=line.split("}",1)
                    section=section.split("{")[1]
                    newLine=section+"\n"
                    label = "\t"*indentLevel+"|"+label
                    newLine+=label
                else:
                    newLine=line.split("{")[1].split("}")[0]
                prefix="\t"*indentLevel
                newLine=prefix+newLine
            else:
                tabs="\t"*indentLevel
                newLine=tabs+"| "+line
            newLines.append(newLine)
        self.markup="\n".join(newLines)
        #print lineType
        #print self.markup
    
class Article(Manager):
    """Manager for an article 


    preamble for article needs no parsing per se
    just put everything before the first \section in the preamble section of
    the otl file.
    """
    def __init__(self, content,fileOut):
        Manager.__init__(self, content, fileOut)

    def parse(self):
        Manager.parse(self)
        self.markup="mu"
        body=[]
        n=len(self.documentLines)
        for i,line in enumerate(self.documentLines):
            #print line.content
            mu=""
            if line.markup:
                mu=line.content.lstrip("|")
                if not mu.isspace():
                    mu=mu.lstrip()
            else:
                section=line.content.strip()
                #lab=""
                #if section.count("@"):
                #    tmp=section.split("@")
                #    section=tmp[0]
                #    label=tmp[1]
                if line.level==0:
                    mu="\n\n\\section{%s}"%line.content.strip()
                elif line.level==1:
                    mu="\n\n\\subsection{%s}"%line.content.strip()
                elif line.level==2:
                    mu="\n\n\\subsubsection{%s}"%line.content.strip()
                else:
                    pass

                # check if next line is a label
                if i < n-1:
                    nextLine=self.documentLines[i+1].content
                    if not nextLine.count("\\label{"):
                        mu+="\n\n" 
            body.append(mu)
        body=" ".join(body)
        self.markup=self.preamble
        self.markup+="\n"+body
        self.markup+="\n\\end{document}"


class ReverseArticle(Manager):
    """Manager for a creating an otl file from  a latex article
    
    """
    def __init__(self, content,fileOut):
        Manager.__init__(self, content, fileOut)


    def parse(self):
        lines=self.content

        # trim blank lines at the end of the file
        while 1:
            if lines[-1].isspace():
                lines.pop()
            else:
                break
        lineTypes=[" "]*len(lines)
        for i,line in enumerate(lines):
            if line.isspace():
                lineTypes[i]='blank'
            else:
                tokens=["\section","\subsection","\subsubsection"]
                lineType='body'
                for token in tokens:
                    if line.count(token):
                        if line.index(token)==0:
                            lineType='header'
                lineTypes[i]=lineType
        n=len(lineTypes)
        blanks=[]
        for i in range(n-2):
            trip=lineTypes[i:i+3]
            if trip[1]=='blank':
                if trip!=["body","blank","body"]:
                    blanks.append(i+1)
        blanks.sort()
        self.markup='blank'
        while blanks:
            p=blanks.pop()
            lines.pop(p)
        self.content=lines[:-1] #get rid of %\end{document}
        mu=""
        indentLevel=0
        newLines=[]
        blanks=[] 
        preamble=True
        newLines.append("preamble")
        for c,line in enumerate(self.content):
            line=line.strip()
            if line.count("\section"):
                preamble=False
            if preamble:
                newLine="\t"+line
            else:
                if line.count("section{"):
                    if line.count("\section"):
                        indentLevel=0
                    elif line.count("\subsection"):
                        indentLevel=1
                    elif line.count("\subsubsection"):
                        indentLevel=2
                    if line.count("}") == 2:
                        section,label=line.split("}",1)
                        section=section.split("{")[1]
                        newLine=section+"\n"
                        label = "\t"*indentLevel+"|"+label
                        newLine+=label
                    else:
                        newLine=line.split("{")[1].split("}")[0]
                    prefix="\t"*indentLevel
                    newLine=prefix+newLine
                else:
                    tabs="\t"*indentLevel
                    newLine=tabs+"| "+line
            newLines.append(newLine)
            self.markup="\n".join(newLines)
    

# Process command line arguments
args = sys.argv
nargs=len(args)
dispatch={}
dispatch['beamer']=Beamer
dispatch['chapter']=Chapter
dispatch['reverseChapter']=ReverseChapter
dispatch['article']=Article
dispatch['reverseArticle']=ReverseArticle
inputFileName=None
outputFileName=None
reverse=0

def printUsage():
    print usage
    sys.exit()

if nargs==1:
    printUsage()
elif nargs>1:
    inputFileName=args[1]
    fname,ext=inputFileName.split(".")
    fin=open(inputFileName,'r')
    content=fin.readlines()
    fin.close()
    if nargs==3:
        fname,oext=outputFileName=args[2].split(".")
    if ext=="otl":
        outputFileName=fname+".tex"
        l1=content[1]
        print l1
        if 'article' in l1:
            docType='article'
        elif 'chapter' in l1:
            docType='chapter'
        elif 'beamer' in l1:
            docType='beamer'
        else:
            print 'bad documentclass'
    else:
        outputFileName=fname+".otl"
        l1=content[0]
        if 'article' in l1:
            docType='reverseArticle'
        elif 'chapter' in l1:
            docType='reverseChapter'
        else:
            print 'unsupported reverse parser'
            print l1

    try:
        dispatch[docType](content,outputFileName)
    except:
        pass

